/**
* @copyright Copyright (c) 2022-2023 Grab GEO IoT Team
* @file persistence_com.c
* @author wenzhu.tu
* @version 0.1
* @date 2023-04-23
* @brief persistence com
*/

/******************************* Includes ********************************/
#include "persistence_com.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/******************************* Macros ******************************/

/******************************* Typedef ******************************/


/******************************* Variables ******************************/


/******************************* Functions ******************************/
static int update_header_to_file(persistence_handle_t *handle)
{ 
    if(handle == NULL || handle->fd < 0)
    {
       PERSIST_E("input args is invlid");
       return -1;
    }

    if(lseek(handle->fd, 0, SEEK_SET) != 0)
    {
        PERSIST_E("lseek fail");
        return -1;
    }

    if(write(handle->fd, (void *)&handle->header, sizeof(persistence_head_t)) < 0)
    {
        PERSIST_E("write header fail");
        return -1;
    }
    fsync(handle->fd);    
    return 0;
}

static int read_header_from_file(persistence_handle_t *handle)
{
    if(handle == NULL || handle->fd < 0)
    {
       PERSIST_E("input args is invlid");
       return -1;
    }

    persistence_head_t header_ = {0};
    if(read(handle->fd,  (void *)&header_, sizeof(persistence_head_t)) < 0)
    {
        PERSIST_E("read header fail");
        return -1;
    }
    else
    {
        if(header_.magic_start == MAGIC_START && header_.magic_end == MAGIC_END)
        {
            memcpy((void *)&handle->header, (void *)&header_, sizeof(persistence_head_t));
        }
<<<<<<< HEAD
=======
        else
        {
            persistence_handle_t temp_handle = {0};
            memcpy((void *)&temp_handle.header, (void *)&header_, sizeof(persistence_head_t));
            PERSIST_E("header is invalid");
            persistence_dump(&temp_handle, PERSIST_DUMP_HEADER);
        }
>>>>>>> fce35a03da8b8eb1530708c7cf62c40c49150153
    }

    return 0;
}

persistence_handle_t *persistence_com_init(int max_persist_size, char *filename,  PERSIST_MODE_e mode)
{
    persistence_handle_t *handle = malloc(sizeof(persistence_handle_t));
    if(handle == NULL)
    {
        PERSIST_E("malloc fail");
        return NULL;
    }
    
    memset(handle, 0, sizeof(persistence_handle_t));
    handle->header.magic_start = MAGIC_START;
    handle->header.magic_end = MAGIC_END;
    handle->header.version = PERSIST_VERSION;
    handle->header.read_index = 0;
    handle->header.write_index = 0;
    handle->header.max_persist_size = max_persist_size;
    handle->fd = -1;
    handle->header.mode = mode;
    handle->unit_array = NULL;
    if(mode == PERSIST_MODE_RAM)
    {
        handle->unit_array = malloc(max_persist_size * sizeof(unit_struct_t));
        if(handle->unit_array == NULL)
        {
            goto FAIL;
        }
    }
    else if(mode == PERSIST_MODE_FILE)
    {
<<<<<<< HEAD
        handle->fd = open(filename, O_RDWR|O_CREAT, 0777);
=======
        handle->fd = open(filename, O_RDWR|O_CREAT|O_SYNC, 0777);
>>>>>>> fce35a03da8b8eb1530708c7cf62c40c49150153
        if(handle->fd < 0)
        {
            PERSIST_E("open file fail, %s", filename);
            goto FAIL;
        }
        //if old file, get header, else sync head to file
        struct stat fileinfo = {0};
        if(stat(filename, &fileinfo) == 0) 
        {
             int head_len = sizeof(persistence_head_t);
             if(fileinfo.st_size >= head_len)
             {
               read_header_from_file(handle);
             }
             //sync head to file
             update_header_to_file(handle);
        }
    }
    return handle;

    FAIL:
    if(handle != NULL)
    {
        if(handle->unit_array != NULL)
        {
           free(handle->unit_array);
        }

        if(handle->fd >= 0)
        {
           close(handle->fd);
        }
        free(handle);
    }
    
    return NULL;
}

int persistence_com_deinit(persistence_handle_t *handle)
{
    if(handle != NULL)
    {
         if(handle->unit_array != NULL)
        {
           free(handle->unit_array);
           handle->unit_array = NULL;
        }
        
        if(handle->fd >= 0)
        {
           fsync(handle->fd);
           close(handle->fd);
        }
        
        free(handle);
    }
    return 0;
}

static int persistence_write_unit(persistence_handle_t *handle, unit_struct_t *unit)
{
    if(handle == NULL)
    {
        PERSIST_E("handle is null");
        return -1;
    }

    if(PERSIST_IS_FULL(handle))
    {
        PERSIST_E("persistence queue is full");
        return -2;
    }

    if(handle->header.mode == PERSIST_MODE_RAM)
    {
        if(handle->unit_array == NULL)
        {
            PERSIST_E("unit_array is null");
            return -1;
        }
        
        memset(&handle->unit_array[handle->header.write_index], 0, sizeof(unit_struct_t));
        memcpy(&handle->unit_array[handle->header.write_index], unit, sizeof(unit_struct_t));
        PERSIST_INC_INDEX(handle->header.write_index, handle->header.max_persist_size);
    }
    else if(handle->header.mode == PERSIST_MODE_FILE)
    {
         if(handle->fd < 0)
         {
            PERSIST_E("fd is invalid");
            return -1;
         }
        int64_t file_pos = sizeof(persistence_head_t) + handle->header.write_index * sizeof(unit_struct_t);
        if(lseek(handle->fd, file_pos, SEEK_SET) != file_pos)
        {
            PERSIST_E("lseek fail");
            return -1;
        }

        if(write(handle->fd, (void *)unit, sizeof(unit_struct_t)) < 0)
        {
            PERSIST_E("write unit fail");
            return -1;
        }

        PERSIST_INC_INDEX(handle->header.write_index, handle->header.max_persist_size);

        if(update_header_to_file(handle) != 0)
        {
            PERSIST_DEC_INDEX(handle->header.write_index, handle->header.max_persist_size);
            PERSIST_E("write head fail");
            return -1;
        }
    }
    
    return 0;
}

static void init_unit_struct(unit_struct_t *unit)
{
    unit->magic_start = MAGIC_START;
    unit->is_valid = 1;
    memset((void *)&unit->usrdata, 0, sizeof(usr_data_t));
    unit->magic_end = MAGIC_END;
}

int persistence_com_push(persistence_handle_t *handle, char *data, int datalen, int id)
{
    if(handle == NULL)
    {
        PERSIST_E("handle is null");
        return -1;
    }

    if(PERSIST_IS_FULL(handle))
    {
        PERSIST_E("persistence queue is full");
        return -2;
    }

    unit_struct_t unit = {0};
    init_unit_struct(&unit);
    unit.usrdata.id = id;
    datalen = datalen > MAX_UNIT_DATA_SIZE ? MAX_UNIT_DATA_SIZE : datalen;
    memcpy(unit.usrdata.data, data, datalen);
    if(persistence_write_unit(handle, &unit) != 0)
    {
        PERSIST_E("write unit fail");
        return -1;
    }
      
    return 0;
}

int persistence_com_get(persistence_handle_t *handle, usr_data_t *usr_data)
{
    unit_struct_t unit = {0};
    if(handle == NULL || usr_data == NULL)
    {
        PERSIST_E("input arg invalid");
        return -1;
    }
    
    if(PERSIST_IS_EMPTY(handle))
    {
        PERSIST_E("persist is empty");
        return -2;
    }

    if(handle->header.mode == PERSIST_MODE_RAM)
    {
        if(handle->unit_array == NULL)
        {
            PERSIST_E("unit_array is null");
            return -1;
        }

        unit = handle->unit_array[handle->header.read_index];
        if(unit.magic_start != MAGIC_START || unit.magic_end != MAGIC_END || unit.is_valid == 0)
        {
            PERSIST_E("cur data invalid");
            PERSIST_INC_INDEX(handle->header.read_index, handle->header.max_persist_size);
            return -1;
        }

        memcpy(usr_data, &unit.usrdata, sizeof(usr_data_t));
    }
    else if(handle->header.mode == PERSIST_MODE_FILE)
    {
        if(handle->fd < 0)
        {
            PERSIST_E("fd is invalid");
            return -1;
        }

        int64_t file_pos = sizeof(persistence_head_t) + handle->header.read_index * sizeof(unit_struct_t);
        if(lseek(handle->fd, file_pos, SEEK_SET) != file_pos)
        {
            PERSIST_E("lseek fail");
            return -1;
        }

        
        if(read(handle->fd,  (void *)&unit, sizeof(unit_struct_t)) < 0)
        {
            PERSIST_E("read fail");
            return -1;
        }

        if(unit.magic_start != MAGIC_START || unit.magic_end != MAGIC_END || unit.is_valid == 0)
        {
            PERSIST_E("read data invalid");
            PERSIST_INC_INDEX(handle->header.read_index, handle->header.max_persist_size);
            update_header_to_file(handle);
            return -1;
        }

        memcpy(usr_data, &unit.usrdata, sizeof(usr_data_t));
    }

    return 0;
}

int persistence_com_pop(persistence_handle_t *handle)
{
    if(handle == NULL || PERSIST_IS_EMPTY(handle))
    {
       return -1;
    }

    if(handle->header.mode == PERSIST_MODE_RAM)
    {
        if(handle->unit_array == NULL)
        {
            PERSIST_E("unit_array is null");
            return -1;
        }

        handle->unit_array[handle->header.read_index].is_valid = 0;
        PERSIST_INC_INDEX(handle->header.read_index, handle->header.max_persist_size);
    }
    else if(handle->header.mode == PERSIST_MODE_FILE)
    {
        if(handle->fd < 0)
        {
            PERSIST_E("fd is invalid");
            return -1;
        }

        int64_t file_pos = sizeof(persistence_head_t) + handle->header.read_index * sizeof(unit_struct_t);
        if(lseek(handle->fd, file_pos, SEEK_SET) != file_pos)
        {
            PERSIST_E("lseek fail");
            return -1;
        }

        unit_struct_t unit = {0};
        if(read(handle->fd,  (void *)&unit, sizeof(unit_struct_t)) < 0)
        {
            PERSIST_E("read fail");
            return -1;
        }       

        unit.is_valid = 0;
        if(lseek(handle->fd, file_pos, SEEK_SET) != file_pos)
        {
            PERSIST_E("lseek fail");
            return -1;
        }   

        if(write(handle->fd, (void *)&unit, sizeof(unit_struct_t)) < 0)
        {
            PERSIST_E("write unit fail");
            return -1;           
        }

        PERSIST_INC_INDEX(handle->header.read_index, handle->header.max_persist_size);
        update_header_to_file(handle);
    }
    return 0;
}

<<<<<<< HEAD
int persistence_dump(PERSIST_DUMP_TYPE_e dump_type)
{
    if(dump_type == PERSIST_DUMP_ALL)
    {
       
=======
int persistence_dump(persistence_handle_t *handle, PERSIST_DUMP_TYPE_e dump_type)
{
    if(handle == NULL)
    {
        return -1;
    }

    if(dump_type == PERSIST_DUMP_HEADER)
    {
       PERSIST_D("magic start: %d\n", handle->header.magic_start);
       PERSIST_D("version: %d\n", handle->header.version);
       PERSIST_D("read index: %d\n", handle->header.read_index);
       PERSIST_D("write index: %d\n", handle->header.write_index);
       PERSIST_D("max persist size: %d\n", handle->header.max_persist_size);
       PERSIST_D("persist mode: %d\n", handle->header.mode);
       PERSIST_D("magic end: %d\n", handle->header.magic_end);
>>>>>>> fce35a03da8b8eb1530708c7cf62c40c49150153
    }
    return 0;
}

/******************************* End of file ******************************/